﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace Project
{
    public abstract class GameEntity
    {

        public float health { get; protected set; }

        public Vector3 position { get; protected set; }
        protected Vector3 velocity;
        private Vector3 acceleration;
        public Quaternion orientation = Quaternion.Identity;
        public Quaternion targetOrientation = Quaternion.Identity;
        protected float slerpConstant = 0.19f;
        protected float frictionConstant = 0.20f;
        protected float dragConstant = 0.005f;
        protected Vector3 gravity = Vector3.Up * 20.0f;
        protected float maxSpeed = 1000; // 1000 units per second
        protected float elasticity = 0.0f; // determines static-geometry-collision response, 0.0 = slide, 1.0 = bounce
        public CollisionObject collisionObject { get; protected set; }

        public GameEntity(Vector3 position)
        {
            this.position = position;
            this.velocity = Vector3.Zero;
            this.acceleration = Vector3.Zero;
            this.orientation = new Quaternion();
            this.targetOrientation = new Quaternion();
            collisionObject = new CollisionObject(new AABB(Vector3.Zero, Vector3.Zero, Vector3.Zero), delegate(CollisionData data) { }, this);
        }
        public GameEntity(Vector3 position, Quaternion orientation)
        {
            this.position = position;
            this.orientation = orientation;
            this.velocity = Vector3.Zero;
            this.acceleration = Vector3.Zero;
            this.orientation = orientation;
            this.targetOrientation = orientation;
            collisionObject = new CollisionObject(new AABB(Vector3.Zero, Vector3.Zero, Vector3.Zero), delegate(CollisionData data) { }, this);
        }

        public GameEntity(Vector3 position, Quaternion orientation, int health) : this(position, orientation)
        {
            this.health = health;
        }

        public virtual void damage(float damage) {
            health -= damage;
        }

        public void applyForce(Vector3 force)
        {
            acceleration += force;
        }

        public virtual bool update(GameTime gt, GameState gs)
        {
            // update physics
            float timeDelta = gt.ElapsedGameTime.Milliseconds / 1000.0f;
            velocity += (acceleration + gravity) * timeDelta;
            if(velocity.Length() > maxSpeed) {
                velocity.Normalize();
                velocity *= maxSpeed;
            }
            Vector3 deltaPosition = velocity * timeDelta;

            // update position along all 3 axes individually to handle collision detection/response more easily
            AABB newBounds = new AABB(collisionObject.collisionBox.min, collisionObject.collisionBox.max, Vector3.Zero);
            Vector3 newPos = position;
            bool collision = false;
            // x axis:
            newPos.X += deltaPosition.X;
            newBounds.position = newPos;
            if (gs.getCollisionManager().collides(newBounds))
            {
                newPos.X = position.X;
                velocity.X *= -elasticity;
                collision = true;
            }
            // y axis:
            newPos.Y += deltaPosition.Y;
            newBounds.position = newPos;
            if (gs.getCollisionManager().collides(newBounds))
            {
                newPos.Y = position.Y;
                velocity.Y *= -elasticity;
                collision = true;
            }
            // z axis:
            newPos.Z += deltaPosition.Z;
            newBounds.position = newPos;
            if (gs.getCollisionManager().collides(newBounds))
            {
                newPos.Z = position.Z;
                velocity.Z *= -elasticity;
                collision = true;
            }
            position = newPos;

            // add friction or drag
            if(collision)
                velocity *= 1.0f - frictionConstant;
            else 
                velocity *= 1.0f - dragConstant;
            // clamp small velocities to 0
            if (velocity.Length() < 0.1)
            {
                velocity = Vector3.Zero;
            }


            // smooth angle interpolation
            Quaternion.Slerp(ref orientation, ref targetOrientation, slerpConstant, out orientation);

            // update collision object locations
            // note that we flip the position vector to compensate for using different coordinate systems
            collisionObject.collisionBox.position = position; //  * -1;

            // interact with the environment
            // make sure we use the position we were going to be at so we properly handle collisions against walls we hit
            collisionObject.collisionBox.position = position + deltaPosition;
            gs.getCollisionManager().interact(collisionObject, gt, gs);

            // handle collisions against the environment
            if (collision)
            {
                this.collisionObject.handler(new CollisionData(null, gt, gs));
            }

            acceleration = Vector3.Zero;
            return true;
        }

        public virtual void init(GameState gs)
        {
            gs.getCollisionManager().addCollisionObject(collisionObject);
        }

        public virtual void destroy(GameState gs)
        {
            gs.getCollisionManager().removeCollisionObject(collisionObject);
        }
    }
}
